#include <iostream>
#include <vector>
#include <thread>
#include <chrono>
#include <algorithm>
#include <unordered_map>

struct background_task 
{
public:
    void operator()() const
    {
        std::cout << "background_task" << std::endl;
    }
};

// 使用RAII手法等待线程结束
class thread_guard
{
private:
    std::thread& t; 
public:
    explicit thread_guard(std::thread& t_) : t(t_) {}
    ~thread_guard()
    {
        if (t.joinable())   // 检查线程是否可等待
        {
            t.join();
        }
    }

    thread_guard(const thread_guard&) = delete;
    thread_guard& operator=(const thread_guard&) = delete;
};

void do_something() {
    using namespace std::chrono_literals;
    // 阻塞当前线程执行, 模拟线程任务
    std::cout << "Hello waiter\n" << std::flush;
    auto start = std::chrono::high_resolution_clock::now();
    std::this_thread::sleep_for(1000ms);
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> elapsed = end-start;
    std::cout << "Waited " << elapsed.count() << " ms\n";
}

// 可以在一个线程中创建另一个线程, 但必须要 join
void fun() {
    std::thread fun_thread(do_something);
    thread_guard g(fun_thread);
}   // 作用域结束后会调用join

void fun1(int a, std::string b) {
    std::cout << a << '-' << b << std::endl;
}

void fun2(int& a, std::string& b) {
    a = 100;
    b = "ref_data";
}

class Foo
{
    int data;
public:
    explicit Foo(int data_) : data(data_) {}
    void setData(int data_) { data = data_; }
    int getData() { return data; }
};

// std::thread 作为函数的返回值
std::thread fun_ret_thread() {
    // 或者返回一个在此函数中定义的 std::thread 也行
    return std::thread{background_task()};
}

void create_some_thread_and_wait() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; i++) {
        threads.push_back(std::thread([=](){ printf("%d\n", i); }));
    }
    // 等待所有线程完成
    for (auto& thread : threads) {
        thread.join();
    }
}

int main(int argc, char* argv[]) {
    // 若想传入临时对象, 需要注意与函数声明区分开
    // std::thread mythread(background_task());    // 会被当成函数声明, 参数是一个函数指针
    std::thread mythread{background_task()};    // 将thread声明为一个对象
    // 主线程等待子线程完成任务
    mythread.join();

    // thread不可复制只可移动, 下面会调用移动构造函数
    mythread = std::thread([](){
        // 调用thread类的静态成员函数, 输出硬件线程数
        std::cout << std::thread::hardware_concurrency() << std::endl;
    });
    mythread.join();
    // mythread.detach();   // 显示决定不等待线程结束, 会把线程丢在后台运行
    // 一种危险的用法, 线程引用了局部变量, 然而局部变量在线程结束之前生命期结束而被销毁了

    // 使用RAII手法
    fun();

    // 执行带参数的线程任务(无引用)
    mythread = std::thread(fun1, 1, std::string("hello"));   // fun1如果有重载会无法区分
    mythread.join();

    // 执行带引用的任务, 希望在子线程通过引用改变数据内容, 需要使用 std::ref 修饰参数
    int a = 10;
    std::string b = "data";
    // 移动或按值复制线程函数的参数。若需要传递引用参数给线程函数，则必须包装它(例如用 std::ref 或 std::cref)
    mythread = std::thread(fun2, std::ref(a), std::ref(b));
    mythread.join();
    std::cout << a << '-' << b << std::endl;    // 100-ref_data

    // 在子线程调用主线程class的成员函数
    Foo f(25);
    mythread = std::thread(&Foo::setData, &f, 35);  // 类似 std::bind 的用法
    mythread.join();
    std::cout << "Foo: data " << f.getData() << std::endl;  // Foo: data 35

    // 转移线程的所有权, 使用移动操作
    std::thread t1(fun);
    std::thread t2; // t2 没有任务关联
    t2 = std::move(t1); // 将 t1 的任务转移到 t2
    t2.join();

    std::thread t3 = fun_ret_thread();
    t3.join();

    // 生成一批线程并等待它们完成
    create_some_thread_and_wait();

    // 线程 id 在c++中用一个class表示, 而非整数
    std::cout << std::this_thread::get_id() << std::endl;   // 打印当前线程的id

    // c++标准库提供了std::hash<std::thread::id>使其用于关联式容器
    std::unordered_map<std::thread::id, std::string> mp;
    mp.insert({std::this_thread::get_id(), "main_thread"});
    std::cout << mp[std::this_thread::get_id()] << std::endl;
    return 0;
}